#include "comm.h"

#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>

static const char * local_ip = "0.0.0.0";
static unsigned short local_port = 0;

static const char * remote_host = 0;
static unsigned short remote_port = 0;

static FILE * log = 0;

void Usage(const char * prog)
{
    printf("Usage : %s [local_ip] local_port [remote_ip remote_port]\n", prog);
    exit(0);
}

typedef struct translate_info
{
    struct translate_info * prev;
    struct translate_info * next;
    int fd1;
    int fd2;
    time_t start_connect;
} translate_info;

translate_info * connecting_list = 0;

static void on_read(network * net, int fd, void * ctx)
{
    char buff[4096] = {0};
    int len = sizeof(buff);

    translate_info * t = (translate_info*)ctx;
    int fd_from = fd;
    int fd_desc = (t->fd1==fd)?t->fd2:t->fd1;

retry:
    switch (len = recv(fd_from, buff, sizeof(buff), 0)) {
        case -1:
            if (errno == EAGAIN ||
                    errno == EWOULDBLOCK||
                    errno == EINPROGRESS) {
                return;
            }

            if (errno == EINTR ) {
                goto retry;
            }
        case 0:
            printf("socket %d closed: %s\n",
                    fd, len?strerror(errno):"by peer");

            network_del(net, fd_from);
            close(fd_from);

            printf("close socket %d\n", fd_desc);
            network_del(net, fd_desc);
            close(fd_desc);

            release_mem(t, sizeof(translate_info));
            return;
        default:
            break;
    }

    send(fd_desc, buff, len, 0);

    if (log) {
        fprintf(log, "tran %d ==> %d, length:%d\n", fd_from, fd_desc, len);
        int i = 0;
        for (i = 0; i < len; i++) {
	    if ( i % 16  == 0) {
		fprintf(log, "\n");
	    }
            fprintf(log, "%02X ", (unsigned char)buff[i]);
        }
        fprintf(log, "\n");
    } else {
        fprintf(stdout, "tran %d ==> %d, length:%d\n", fd_from, fd_desc, len);
    }
}

static void on_connected(network * net, int fd, int error, void * ctx)
{
    translate_info * t = (translate_info*)ctx;
    dlist_remove(connecting_list, t);

    if(error == 0) {
        printf("connected success : %d <---> %d\n", t->fd1, t->fd2);
        if (remote_host == 0) {
            send(t->fd1, "OK\r\n", 4, 0);
        }
        network_set(net, t->fd1, on_read, 0, t);
        network_set(net, t->fd2, on_read, 0, t);
    } else {
        printf("connect failed : %d <-x-> %d\n", t->fd1, t->fd2);
        printf("close socket:%d\n", t->fd1);
        printf("close socket:%d\n", t->fd2);
        network_del(net, t->fd1);
        network_del(net, t->fd2);
        close(t->fd1);
        close(t->fd2);
        release_mem(t, sizeof(translate_info));
    }
}

static int connect_to(network * net, int fd, const char * host, int port)
{
    translate_info * t = (translate_info*)get_mem(sizeof(translate_info));
    if (t == 0) {
        printf("get_mem failed\n");
        goto failed;
   }

    t->start_connect = time(0);

    network_del(net, fd);

    t->fd1 = fd;
    t->fd2 = network_connect(net, host, port, on_connected, t);
    if (t->fd2 == -1) {
        printf("connect to %s:%u failed: %s", host, port, strerror(errno));
        goto failed;
    } 

    dlist_insert_tail(connecting_list, t);
    printf("connect to %s:%u(socket:%d) by socket:%d\n",
            host, port, t->fd2, t->fd1);
    return 0;

failed:
    if (t) { 
        dlist_remove(connecting_list, t);

        if (t->fd2 != -1) {
            printf("close socket:%d\n", t->fd2);
            network_del(net, t->fd2);
            close(t->fd2);
        }
        release_mem(t, sizeof(translate_info));
    }

    printf("close socket:%d\n", fd);
    network_del(net, fd);
    close(fd);
    return -1;

}
 
static void on_first_packet(network * net, int fd, void * ctx)
{
    char buff[256] = {0};
    char * sep = buff;
    char host[256] = {0};
    unsigned short port = 0;
    
    int len = recv(fd, buff, sizeof(buff) - 1, 0);
    switch (len) {
        case -1:
        case 0:
            printf("socket:%d recv remote address failed\n", fd);
            goto failed;
        default:
            break;
    }

    if ( len <= 0 || buff[len-1] != '\n') {
        printf("socket:%d remote address formaterror\n", fd);
        goto failed;
    }

    sep = buff;
    while ( *sep != '\n' ) {
        if (*sep == ':' ) {
            break;
        }
        if ( *sep != '.' &&
                ( *sep < '0' || *sep > '9') ) {
            printf("socket:%d remote address format error\n", fd);
            goto failed;
        }
        sep++;
    }

    *sep = 0;
    sep++;

    strcpy(host, buff);
    port = atoi(sep);
    if (port == 0) {
        printf("socket:%d remote address port error\n", fd);
        goto failed;
    }

    connect_to(net, fd, host, port);
    return;
failed:
    printf("close socket:%d\n", fd);
    network_del(net, fd);
    close(fd);
    return ;

}

void on_accept(network * net, int listen_fd, int new_fd,
        const char * ip, unsigned short port, void * ctx)
{
    printf("%s:%u socket:%d connected\n", ip, port, new_fd);

    if (remote_host) {
        connect_to(net, new_fd, remote_host, remote_port);
    } else {
        if (network_set(net, new_fd, on_first_packet, 0, 0) ) {
            printf("network_set socket:%d failed\n", new_fd);
            close(new_fd);
        }
    }
}

void on_timer(network * net, time_t now, void * ctx)
{
    translate_info * xlist = connecting_list;
    connecting_list = 0;
    while (xlist) {
        translate_info * t = xlist;
        dlist_remove(xlist, t);
        if (t->start_connect + 5 < now) {
            printf("connection %d <-.-> %d timeout\n", t->fd1, t->fd2);
            printf("close socket:%d\n", t->fd1);
            printf("close socket:%d\n", t->fd2);
            network_del(net, t->fd1);    
            network_del(net, t->fd2); 
            close(t->fd1);
            close(t->fd2);
            release_mem(t, sizeof(translate_info));
        } else {
            dlist_insert_tail(connecting_list, t);
        }
    }
}


network * net = 0;
static void handle_signal(int sig)
{
    network_exit(net);
}

int main(int argc, char * argv[])
{
    signal(SIGTERM, handle_signal);
    signal(SIGINT, handle_signal);

    log = stdout;

    switch(argc) {
        case 2:
            local_port = atoi(argv[1]);
            break;
        case 3:
            local_ip = argv[1];
            local_port = atoi(argv[2]);
            break;
        case 4:
            local_port = atoi(argv[1]);
            remote_host = argv[2];
            remote_port = atoi(argv[3]);
            break;
        case 5:
            local_ip = argv[1];
            local_port = atoi(argv[2]);
            remote_host = argv[3];
            remote_port = atoi(argv[4]);
            break;
        default:
            Usage(argv[0]);
    }

    net = network_create(NETWORK_LT);
    if (net == 0) {
        printf("network_create failed\n");
        return -1;
    }

    network_timer(net, on_timer, 0);

    if (network_listen(net, local_ip, local_port, on_accept, 0) == -1) {
        printf("listen on %s:%u failed: %s",
                local_ip, local_port, strerror(errno));
        network_destory(net);
        return -1;
    }

    network_loop(net);

    network_destory(net);
    net = 0;

    printf("exit\n");
    return 0;
}
